/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.diagram.statemachine.command;

import java.util.Vector;

import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.commands.CompoundCommand;
import org.eclipse.gef.requests.ChangeBoundsRequest;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Region;

import com.cea.papyrus.core.commands.GraphNodeSetConstraintCommand;
import com.cea.papyrus.core.utils.Debug;
import com.cea.papyrus.diagram.statemachine.helper.NodeRegionHelper.Zone;
import com.cea.papyrus.diagraminterchange2.di2.GraphNode;
import com.cea.papyrus.diagraminterchange2.di2.Uml1SemanticModelBridge;

/**
 * A command to specifically resize graph nodes attached to a region.
 * 
 * @author David Servat
 * @since 29 jan. 07
 */
public class ResizeRegionNodeCommand extends Command {

	/**
	 * Debug channel.
	 */
	private int channel = 501;

	/**
	 * The region to resize.
	 */
	private GraphNode region = null;

	/**
	 * The request to perform.
	 */
	private ChangeBoundsRequest request = null;

	/**
	 * The compound command to perform resize.
	 */
	private CompoundCommand compoundCommand = null;

	/**
	 * The constructor.
	 * 
	 * @param regionToResize the graph node associated to the region
	 * @param requestTobePerformed the request which triggered this resize
	 * @param requestToPerform 
	 */
	public ResizeRegionNodeCommand(GraphNode regionToResize, ChangeBoundsRequest requestToPerform){
		Debug.debug(this, "call ResizeRegionNodeCommand(GraphNode regionToResize, ChangeBoundsRequest requestToPerform)", channel);

		//a bunch of precautionary tests
		if((regionToResize == null) || (requestToPerform == null))
			throw new IllegalArgumentException();

		if(regionToResize.getSemanticModel() == null) 
			throw new IllegalArgumentException();

		Element elt = ((Uml1SemanticModelBridge)regionToResize.getSemanticModel()).getElement();
		if(elt == null)
			throw new IllegalArgumentException();

		if(!(elt instanceof Region))
			throw new IllegalArgumentException();

		//now we are sure to have received a correct graph node linked to a region
		//we store the infos
		region = regionToResize;
		request = requestToPerform;
	}

	/**
	 * Calls the redo() method, hence the internaly computed compoundCommand is recomputed.
	 * This deals with the case when undo/redo have been performed on a region creation
	 * and the resize command may no longer be valid.
	 */
	@Override
	public void execute(){
		Debug.debug(this, "call ResizeRegionNodeCommand.execute()", channel);

		redo();
	}

	/**
	 * This rather long method actually computes the compound command to perform resize.
	 * It consists in testing the direction of resize and perform various translation/scaling
	 * on the adjacent and underlying nodes w.r.t. to their positions to the borders of the region node.
	 * The code is long because the symmetry of processing is not easy to figure out.
	 * Explicit duplicated codes is better for comprehension.
	 * At the end the compundCommand is executed.
	 * Note: initializations have to take place just before execution of the command as there might have been
	 * some changes in the underlying structure - e.g. when undo/redo region creation have been made.
	 */
	@Override
	public void redo(){
		Debug.debug(this, "call ResizeRegionNodeCommand.redo()", channel);

		//a bunch of initializations
		int direction = request.getResizeDirection();
		int dx = request.getSizeDelta().width;
		int dy = request.getSizeDelta().height;

		//the todo list will be used as temporary variable
		Vector<GraphNode> todo = new Vector<GraphNode>();

		Rectangle rect;
		GraphNodeSetConstraintCommand command;
		compoundCommand = new CompoundCommand();

		//depending on the direction of resize there are different sclaing/translation to be performed
		//on the nodes sitting on either side of the border which is to be moved
		//the various cases follow the same structure
		//first: retrive a list of node on a given side of the border
		//second: add individual resize commands to be performed on them
		switch(direction){
		case PositionConstants.WEST:
			//a resize that moves a LEFT border
			Debug.debug(this, "Region WEST expansion", channel);

			//retrieve the nodes on the LEFT side of LEFT border
			todo = Zone.getLeftNeighboursOfRegionLeftBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add (-dx) to their width
				rect = new Rectangle(current.getPosition().getCopy(), current.getSize().getCopy().expand(-dx, 0));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			//retrieve the nodes on the RIGHT side of LEFT border
			todo = Zone.getRightNeighboursOfRegionLeftBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add dx to their width and translate their x of (-dx)
				rect = new Rectangle(current.getPosition().getCopy().getTranslated(-dx, 0), current.getSize().getCopy().expand(dx, 0));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			break;
		case PositionConstants.EAST:
			//a resize that moves a RIGHT border
			Debug.debug(this, "Region EAST expansion", channel);

			//retrieve the nodes on the LEFT side of RIGHT border
			todo = Zone.getLeftNeighboursOfRegionRightBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add dx to their width
				rect = new Rectangle(current.getPosition().getCopy(), current.getSize().getCopy().expand(dx, 0));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			//retrieve the nodes on the RIGHT side of RIGHT border
			todo = Zone.getRightNeighboursOfRegionRightBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add (-dx) to their width and translate their x of dx
				rect = new Rectangle(current.getPosition().getCopy().getTranslated(dx, 0), current.getSize().getCopy().expand(-dx, 0));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			break;
		case PositionConstants.NORTH:
			//a resize that moves a TOP border
			Debug.debug(this, "Region NORTH expansion", channel);

			//retrieve the nodes on the BOTTOM side of TOP border
			todo = Zone.getBottomNeighboursOfRegionTopBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add dy to their height and (-dy) to their y
				rect = new Rectangle(current.getPosition().getCopy().getTranslated(0, -dy), current.getSize().getCopy().expand(0, dy));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			//retrieve the nodes on the TOP side of TOP border
			todo = Zone.getTopNeighboursOfRegionTopBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add (-dy) to their height
				rect = new Rectangle(current.getPosition().getCopy(), current.getSize().getCopy().expand(0, -dy));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			break;
		case PositionConstants.SOUTH:
			//a resize that moves a BOTTOM border
			Debug.debug(this, "Region SOUTH expansion", channel);

			//retrieve the nodes on the TOP side of BOTTOM border
			todo = Zone.getTopNeighboursOfRegionBottomBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add dy to their height
				rect = new Rectangle(current.getPosition().getCopy(), current.getSize().getCopy().expand(0, dy));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			//retrieve the nodes on the BOTTOM side of BOTTOM border
			todo = Zone.getBottomNeighboursOfRegionBottomBorder(region);
			for(int i=0; i<todo.size();i++){
				GraphNode current = todo.get(i);
				//for each of these we add (-dy) to their height
				rect = new Rectangle(current.getPosition().getCopy().getTranslated(0, dy), current.getSize().getCopy().expand(0, -dy));
				//and create a unit command to do that
				command = new GraphNodeSetConstraintCommand(current, request, rect);
				//then add the unit command to the compound command
				compoundCommand.add(command);
			}
			break;
		}
		compoundCommand.execute();
	}

	/**
	 * This method amounts to perform an undo() on the internaly computed compoundCommand.
	 */
	@Override
	public void undo(){
		Debug.debug(this, "call ResizeRegionNodeCommand.execute()", channel);

		compoundCommand.undo();
	}

}
